package org.jetlinks.pro.assets.impl.web;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.hswebframework.web.authorization.DimensionType;
import org.hswebframework.web.authorization.annotation.QueryAction;
import org.hswebframework.web.authorization.annotation.Resource;
import org.hswebframework.web.authorization.annotation.ResourceAction;
import org.hswebframework.web.authorization.exception.AccessDenyException;
import org.hswebframework.web.authorization.simple.SimpleDimensionType;
import org.jetlinks.pro.assets.*;
import org.jetlinks.pro.assets.crud.CrudAssetPermission;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuple3;
import reactor.util.function.Tuple4;
import reactor.util.function.Tuples;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/assets")
@Resource(id = "assets-bind", name = "资产绑定")
@Tag(name = "资产绑定")
@AllArgsConstructor
public class AssetBindController {

    private final AssetBindManager bindManager;

    //在url中指定资产类型,方便微服务时,根据资产类型路由到不同的微服务
    @PostMapping("/bind/{assetType}")
    @ResourceAction(id = "bind", name = "绑定")
    @Operation(summary = "绑定")
    public Mono<Void> bind(@PathVariable @Parameter(description = "资产类型") String assetType,
                           @RequestBody Flux<AssetBindRequest> requestFlux) {

        AssetType type = AssetType.of(assetType);

        Flux<AssetBindRequest> cache = requestFlux
            .doOnNext(request -> {
                request.setAssetType(assetType);
                //不允许分配资产为owner
                if (AssetRelation.owner.equals(request.getRelation())) {
                    throw new AccessDenyException("error.can_not_bind_owner_relation");
                }
            })
            .cache();

        return AssetsHolder
            .currentReactive()
            .flatMap(holder -> cache
                .flatMap(request -> {
                    //不能越权绑定
                    Set<AssetPermission> permissions = request.isAllPermission()
                        ? new HashSet<>(type.getPermissions())
                        : new HashSet<>(type.getPermissions(request.getPermission()));
                    //有共享权限才能绑定
                    permissions.add(CrudAssetPermission.share);

                    return holder.assertPermission(type,
                                                   request.getAssetIdList(),
                                                   false,
                                                   permissions.toArray(new AssetPermission[0]));
                })
                .then())
            .thenMany(cache)
            .as(bindManager::bindAssets)
            .as(AssetsUtils.setMonoBindSource(AssetBindManager.SOURCE_MANUAL));

    }

    //在url中指定资产类型,方便微服务时,根据资产类型路由到不同的微服务
    @PostMapping("/unbind/{assetType}")
    @ResourceAction(id = "unbind", name = "解绑")
    @Operation(summary = "解绑")
    public Mono<Void> unbind(@PathVariable @Parameter(description = "资产类型") String assetType,
                             @RequestBody Flux<AssetUnbindRequest> requestFlux) {

        AssetType type = AssetType.of(assetType);

        Flux<AssetUnbindRequest> cache = requestFlux
            .doOnNext(request -> request.setAssetType(assetType))
            .cache();

        return AssetsHolder
            .currentReactive()
            .flatMap(holder -> cache
                .flatMap(request -> Flux
                    .merge(
                        bindManager
                            .getBindings(request.getTargetType(),
                                         Collections.singleton(request.getTargetId()),
                                         type,
                                         request.getAssetIdList())
                            .doOnNext(binding -> {
                                //不能解绑owner的资产
                                if (AssetRelation.owner.equals(binding.getRelation())) {
                                    throw new AccessDenyException("error.can_not_unbind_owner_assets");
                                }
                            })
                            .then(),
                        holder
                            //有共享权限的才能进行解绑
                            .assertPermission(type,
                                              request.getAssetIdList(),
                                              false,
                                              CrudAssetPermission.share)
                    ).then()
                ).then())
            .thenMany(cache)
            .as(bindManager::unbindAssets)
            .as(AssetsUtils.setMonoBindSource(AssetBindManager.SOURCE_MANUAL));
    }

    @PutMapping("/permission/{assetType}/{assetId}/{targetType}/{targetId}")
    @ResourceAction(id = "permission", name = "修改权限")
    @Operation(summary = "修改权限")
    public Mono<Void> updatePermission(@PathVariable @Parameter(description = "资产类型") String assetType,
                                       @PathVariable @Parameter(description = "资产ID") String assetId,
                                       @PathVariable @Parameter(description = "绑定维度类型") String targetType,
                                       @PathVariable @Parameter(description = "绑定维度ID") String targetId,
                                       @RequestBody Mono<List<String>> permissions) {
        AssetType type = AssetType.of(assetType);
        Mono<AssetPermission[]> parsedPermission = permissions
            .map(strList -> type.getPermissions(strList).toArray(new AssetPermission[0]))
            .cache();

        return parsedPermission
            .flatMap(assetPermissions -> Flux
                .merge(
                    bindManager
                        .getBindings(targetType,
                                     Collections.singleton(targetId),
                                     type,
                                     Collections.singleton(assetId))
                        .doOnNext(binding -> {
                            //不能修改owner资产的权限
                            if (AssetRelation.owner.equals(binding.getRelation())) {
                                throw new AccessDenyException("error.can_not_update_owner_assets");
                            }
                        })
                        .then(),
                    AssetsHolder
                        //有共享权限的才能进行权限分配，并且不能越权修改
                        .assertPermission(Flux.just(assetId),
                                          type,
                                          Function.identity(),
                                          false,
                                          AssetPermission.merge(assetPermissions, CrudAssetPermission.share))
                ).then())
            .then(parsedPermission)
            .flatMap(assetPermissions -> bindManager
                .changePermission(
                    targetType,
                    Collections.singleton(targetId),
                    type,
                    Collections.singleton(assetId),
                    assetPermissions
                )
            );
    }

    @PostMapping("/bindings/{assetType}/{targetType}/{targetId}/_query")
    @QueryAction
    @Operation(summary = "查询绑定信息")
    public Flux<AssetBindingView> getAssetBindings(@PathVariable @Parameter(description = "资产类型") String assetType,
                                                   @PathVariable @Parameter(description = "绑定维度类型") String targetType,
                                                   @PathVariable @Parameter(description = "绑定维度ID") String targetId,
                                                   @RequestBody Mono<List<String>> assetIds) {
        AssetType type = AssetType.of(assetType);

        return assetIds
            .flatMapMany(assetIdList -> AssetsHolder
                .currentReactive()
                .map(holder -> holder
                    //获取资产持有人的绑定信息
                    .getBindings(type, assetIdList)
                    .map(AssetBindingView::ofGrantedIsAll)
                )
                //没有holder信息说明是系统级别的用户
                .defaultIfEmpty(
                    bindManager
                        .getBindings(targetType, Collections.singleton(targetId), type, assetIdList)
                        .map(AssetBindingView::of)
                )
                .flatMapMany(flux -> Flux
                    .concat(
                        Flux.fromIterable(AssetBindingView.of(type, targetType, targetId, assetIdList)),
                        flux
                    ))
                .groupBy(AssetBindingView::getAssetId)
                .flatMap(group -> group.reduce(AssetBindingView::merge))
            );


    }

    @GetMapping("/bindings/{assetType}/permissions")
    @QueryAction
    @Operation(summary = "查询资产类型的权限定义")
    public Flux<AssetPermissionView> getAssetTypePermission(@PathVariable @Parameter(description = "资产类型") String assetType) {

        return Flux
            .fromIterable(AssetType
                              .of(assetType)
                              .getPermissions())
            .map(AssetPermissionView::of);


    }
}
